package ace.module.oauth2.server.core.autoconfig.config;

import ace.cmp.security.core.impl.constant.Oauth2SecurityConstant;
import ace.module.oauth2.server.core.impl.authentication.Introspection.IntrospectionAuthenticationProvider;
import ace.module.oauth2.server.core.impl.authorization.oidc.authentication.OidcUserInfoMapper;
import ace.module.oauth2.server.core.impl.authorization.token.Oauth2AccessTokenGenerator;
import ace.module.oauth2.server.core.impl.authorization.token.Oauth2RefreshTokenGenerator;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationConverter;

/**
 * @author caspar
 * @date 2023/2/6 20:09 自动配置oauth2 authorization server
 */
@Slf4j
@AllArgsConstructor
@EnableConfigurationProperties({OAuth2ResourceServerProperties.class})
@Configuration
public class Oauth2ServerSecurityConfig {

  @Bean
  @Order(Oauth2SecurityConstant.OAUTH2_AUTHORIZATION_SERVER_AUTOCONFIGURE_ORDER)
  public SecurityFilterChain authorizationServerSecurityFilterChain(
      HttpSecurity httpSecurity,
      OAuth2AuthorizationServerConfigurer oAuth2AuthorizationServerConfigurer,
      OAuth2ResourceServerProperties oAuth2ResourceServerProperties,
      ApplicationContext applicationContext)
      throws Exception {

    httpSecurity
        .securityMatcher(oAuth2AuthorizationServerConfigurer.getEndpointsMatcher())
        .authorizeHttpRequests(
            authorizeHttpRequests -> authorizeHttpRequests.anyRequest().authenticated())
        .csrf()
        .disable()
        .apply(oAuth2AuthorizationServerConfigurer);

    return httpSecurity.build();
  }

  @Bean
  public OAuth2AuthorizationServerConfigurer oAuth2AuthorizationServerConfigurer(
      HttpSecurity httpSecurity,
      ObjectProvider<OidcUserInfoMapper> oidcUserInfoMapperObjectProvider,
      IntrospectionAuthenticationProvider introspectionAuthenticationProvider,
      List<AuthenticationConverter> authenticationConverters,
      List<AuthenticationProvider> authenticationProviders) {
    OAuth2AuthorizationServerConfigurer oAuth2AuthorizationServerConfigurer =
        new OAuth2AuthorizationServerConfigurer();

    oAuth2AuthorizationServerConfigurer
        // 开启openid connect
        .oidc(
            oidcConfigurer -> {
              oidcConfigurer.userInfoEndpoint(
                  oidcUserInfoEndpointConfigurer -> {
                    OidcUserInfoMapper oidcUserInfoMapper =
                        oidcUserInfoMapperObjectProvider.getIfAvailable();
                    if (oidcUserInfoMapper != null) {
                      oidcUserInfoEndpointConfigurer.userInfoMapper(oidcUserInfoMapper);
                    }
                  });
            })
        .tokenEndpoint(
            endpointConfigurer -> {
              Optional.ofNullable(authenticationConverters).stream()
                  .flatMap(List::stream)
                  .forEach(endpointConfigurer::accessTokenRequestConverter);

              Optional.ofNullable(authenticationProviders).stream()
                  .flatMap(List::stream)
                  .forEach(endpointConfigurer::authenticationProvider);
            })
        .tokenIntrospectionEndpoint(
            configurer -> configurer.authenticationProvider(introspectionAuthenticationProvider));
    return oAuth2AuthorizationServerConfigurer;
  }

  @Bean
  @ConditionalOnMissingBean
  public OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator(JwtEncoder jwtEncoder) {
    JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);

    Oauth2AccessTokenGenerator accessTokenGenerator = new Oauth2AccessTokenGenerator();

    Oauth2RefreshTokenGenerator refreshTokenGenerator = new Oauth2RefreshTokenGenerator();

    OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator =
        new DelegatingOAuth2TokenGenerator(
            jwtGenerator, accessTokenGenerator, refreshTokenGenerator);

    return tokenGenerator;
  }

  @Bean
  @ConditionalOnMissingBean
  public JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
    JwtEncoder jwtEncoder = new NimbusJwtEncoder(jwkSource);
    return jwtEncoder;
  }

  /**
   * 授权服务器配置
   */
  @Bean
  @ConditionalOnMissingBean
  public AuthorizationServerSettings authorizationServerSettings() {
    return AuthorizationServerSettings.builder().build();
  }

  @Bean
  @ConditionalOnMissingBean
  public JWKSource<SecurityContext> jwkSource() {
    KeyPair keyPair = generateRsaKey();
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    RSAKey rsaKey =
        new RSAKey.Builder(publicKey)
            .privateKey(privateKey)
            .keyID(UUID.randomUUID().toString())
            .build();
    JWKSet jwkSet = new JWKSet(rsaKey);
    return new ImmutableJWKSet<>(jwkSet);
  }

  private static KeyPair generateRsaKey() {
    KeyPair keyPair;
    try {
      KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
      keyPairGenerator.initialize(2048);
      keyPair = keyPairGenerator.generateKeyPair();
    } catch (Exception ex) {
      throw new IllegalStateException(ex);
    }
    return keyPair;
  }

  @Bean
  @ConditionalOnMissingBean
  public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
    return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
  }
}
